#include "callother.h"

CoValue *CoModule_New(CoValue *mod_name, CoValue *functions, CoValue *strings, int num_global, char *code_start, int code_size)
{
    int i;
    CoValue *func;
    CoModule *mod;

    mod = (CoModule *)CO_MALLOC(sizeof(CoModule));
    if (mod == NULL) {
        /* TODO: [ERROR] no memory */
        return NULL;
    }
    COVALUE_INIT(mod, &CoModule_Type);

    mod->m_name = mod_name;
    CO_INCREF(mod_name);

    mod->m_code_size = code_size;
    mod->m_code = (char *)CO_MALLOC(code_size * sizeof(char));
    memcpy(mod->m_code, code_start, code_size);

    mod->m_func_map = CoDict_New();
    for (i = 0; i < CoList_GET_SIZE(functions); i++) {
        func = CoList_GetItem(functions, i);
        CoDict_SetItem(mod->m_func_map, CoFunc_GET_NAME(func), func);
    }

    mod->m_functions = functions;
    CO_INCREF(functions);

    mod->m_strings = strings;
    CO_INCREF(strings);

    mod->m_num_global = num_global;
    if (num_global) {
        mod->m_globals = (CoValue**)CO_MALLOC(num_global * sizeof(CoValue*));
        /* initialize globals */
        for (i = 0; i < num_global; i++) {
            mod->m_globals[i] = CoInt_FromInt(0);
        }
    } else {
        mod->m_globals = 0;
    }

    return (CoValue *)mod;
}

CoValue *CoModule_GetFunctionByName(CoValue *self, CoValue *func_name)
{
    CoValue *func_map;

    assert(COMODULE_CHECK(self));
    func_map = ((CoModule *)self)->m_func_map;

    return CoDict_GetItem(func_map, func_name);
}

CoValue *CoModule_GetFunctionByIndex(CoValue *self, int index)
{
    CoValue *functions;

    assert(COMODULE_CHECK(self));
    functions = ((CoModule *)self)->m_functions;

    assert(index >= 0 && index < CoList_GET_SIZE(functions));
    return CoList_GetItem(functions, index);
}

CoValue *CoModule_GetStringByIndex(CoValue *self, int index)
{
    CoValue *strings;

    assert(COMODULE_CHECK(self));
    strings = ((CoModule *)self)->m_strings;

    assert(index >= 0 && index < CoList_GET_SIZE(strings));
    return CoList_GetItem(strings, index);
}

CoValue **CoModule_GetGlobalPtrByIndex(CoValue *self, int index)
{
    assert(COMODULE_CHECK(self));

    assert(index >= 0 && index < ((CoModule *)self)->m_num_global);
    return ((CoModule *)self)->m_globals + index;
}

#define EXTRACT_UCHAR(p)    (*(unsigned char *)(p))

void print_code(CoModule *mod, char *pc, char *pc_end)
{
    int instruction;

    while (pc < pc_end) {
        instruction = *pc++;

        if (instruction < 0 || instruction >= OPCODE_NUM) {
            printf("Unknown opcode: %d.\n", instruction);
            break;
        }
        printf("    %-30s", g_opcode_repr[instruction]);

        switch (instruction)
        {
            default:
                {
                    break;
                }
            case F_LOCAL_LVALUE:
                {
                    int index = 0;

                    index = EXTRACT_UCHAR(pc++);
                    printf("%d", index);
                    break;
                }
            case F_GLOBAL_LVALUE:
                {
                    int index = 0;

                    index = EXTRACT_UCHAR(pc++);
                    printf("%d", index);
                    break;
                }
            case F_NUMBER:
                {
                    int val = 0;

                    LOAD_INT(val, pc);
                    printf("%d", val);
                    break;
                }
            case F_LOCAL:
                {
                    int index = 0;

                    index = EXTRACT_UCHAR(pc++);
                    printf("%d", index);
                    break;
                }
            case F_GLOBAL:
                {
                    int index = 0;

                    index = EXTRACT_UCHAR(pc++);
                    printf("%d", index);
                    break;
                }
            case F_VOID_ASSIGN_LOCAL:
                {
                    int index = 0;

                    index = EXTRACT_UCHAR(pc++);
                    printf("%d", index);
                    break;
                }
            case F_CALL_FUNCTION_BY_ADDRESS:
                {
                    int index = 0;
                    int num_arg = 0;
                    CoValue *func;

                    LOAD_SHORT(index, pc);
                    num_arg = EXTRACT_UCHAR(pc++);
                    func = CoList_GetItem(mod->m_functions, index);
                    printf("%d (%s) [args=%d]",
                            index,
                            CoString_AS_STRING(CoFunc_GET_NAME(func)),
                            num_arg);
                    break;
                }
            case F_STRING:
                {
                    int index = 0;
                    CoValue *sp;

                    LOAD_SHORT(index, pc);
                    sp = CoList_GetItem(mod->m_strings, index);
                    printf("%d (%s)", index, CoString_AS_STRING(sp));
                    break;
                }
            case F_EFUNV:
                {
                    int num_arg;

                    num_arg = EXTRACT_UCHAR(pc++);
                    printf("%d (%d)", num_arg, EXTRACT_UCHAR(pc++));
                    break;

                }
        }
        printf("\n");
    }
}

void CoModule_DumpCode(CoValue *self)
{
    int i;
    CoModule *mod;
    CoFunc *func;

    assert(COMODULE_CHECK(self));
    mod = (CoModule *)self;

    printf("======= Module %s\n", CoString_AS_STRING(mod->m_name));
    print_code(mod, mod->m_code, mod->m_code + mod->m_code_size);

    for (i = 0; i < CoList_GET_SIZE(mod->m_functions); i++) {
        func = (CoFunc *)CoList_GetItem(mod->m_functions, i);
        printf("---------- Function %s\n", CoString_AS_STRING(func->f_name));
        print_code(mod, func->f_code, func->f_code + func->f_code_size);
    }

    printf("====== End of Module %s\n", CoString_AS_STRING(mod->m_name));
}

static void mod_dealloc(CoValue *self)
{
    int i;
    CoModule *mod;

    assert(COMODULE_CHECK(self));
    mod = (CoModule *)self;

	CO_DECREF(mod->m_name);
	CO_DECREF(mod->m_functions);
    CO_DECREF(mod->m_func_map);
	CO_DECREF(mod->m_strings);

    for (i = 0; i < mod->m_num_global; i++) {
        CO_DECREF(mod->m_globals[i]);
    }
	CO_FREE(mod->m_globals);

    CO_FREE(mod->m_code);

	CoValue_Free(self);
}

CoType CoModule_Type = {
    "module",
	mod_dealloc,                    /* tp_dealloc */
    0,                              /* tp_hash */
    0,                              /* tp_equal */
    0,                              /* tp_str */
    0,                              /* tp_iter */
    0,                              /* tp_iternext */
};
